/*
 * Created on 2004-05-26
 */
package org.opencube.oms.mapping.generator;

import info.fingo.util.Text;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.opencube.oms.OMSData;
import org.opencube.oms.meta.Namespace;
import org.opencube.oms.meta.OMSMetaData;
import org.opencube.oms.meta.Scheme;
import org.opencube.util.Constants;

/**
 * @author <a href="mailto:maciek@fingo.pl">FINGO - Maciej Mroczko</a>
 * TODO: comment
 */
public class MetaDataGenerator
{
	private static final String USER_ID = Constants.STR_ZERO;

	private static final String SQL_MAIN_TEMPLATE = "org/opencube/oms/mapping/generator/templates/sql/metadata/metadata.vm";

	private static final String JAVA_BASE_ENTITY_TEMPLATE = "org/opencube/oms/mapping/generator/templates/java/base_entity.vm";
	private static final String JAVA_ENTITY_TEMPLATE = "org/opencube/oms/mapping/generator/templates/java/entity.vm";
	private static final String JAVA_BASE_FINDER_TEMPLATE = "org/opencube/oms/mapping/generator/templates/java/base_finder.vm";
	private static final String JAVA_FINDER_TEMPLATE = "org/opencube/oms/mapping/generator/templates/java/finder.vm";

	private static final String PHP_BASE_ENTITY_TEMPLATE = "org/opencube/oms/mapping/generator/templates/php/base_entity.vm";
	private static final String PHP_ENTITY_TEMPLATE = "org/opencube/oms/mapping/generator/templates/php/entity.vm";
	private static final String PHP_BASE_FINDER_TEMPLATE = "org/opencube/oms/mapping/generator/templates/php/base_finder.vm";
	private static final String PHP_FINDER_TEMPLATE = "org/opencube/oms/mapping/generator/templates/php/finder.vm";

	private static VelocityEngine getEngine() throws Exception
	{
		VelocityEngine ve = new VelocityEngine();
		Properties p = new Properties();
		p.setProperty( "resource.loader", "classpath" );
		p.setProperty( "classpath.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader" );
		ve.init( p );
		return ve;
	}
	
//	private static final String SQL_TEMPLATES_BASE = "c:/projects/fingo/smart/oms/src/templates/sql/metadata";
//	
//	private static final String SQL_OUTPUT_FILE = "c:/metadata.sql";
//	private static final String SQL_INPUT_FILE = "c:/tmp/oms11_metadata.xml";
//	
//	private static final String JAVA_TEMPLATES_BASE = "c:/projects/fingo/smart/oms/src/templates/java";
//	
//	private static final String JAVA_SOURCE_BASE_DIR = "C:/projects/fingo/smart/oms/src/java";
//	private static final String JAVA_ENTITY_BASE_PACKAGE = "biz.fingo.opencube.oms.entity";
//	private static final String JAVA_FINDER_BASE_PACKAGE = "biz.fingo.opencube.oms.finder";
//
//	private static final String PHP_TEMPLATES_BASE = "c:/projects/fingo/smart/oms/src/templates/php";
//
//	private static final String PHP_SOURCE_BASE_DIR = "C:/temp/php";
//	//private static final String PHP_BASE_ENTITY_BASE_PACKAGE = "metamodel/v10";
//	private static final String PHP_BASE_ENTITY_BASE_PACKAGE = "mmg/whoiswhodb/ent/v10";
//	private static final String PHP_BASE_FINDER_BASE_PACKAGE = "mmg/whoiswhodb/db/v10";
//	private static final String PHP_ENTITY_BASE_PACKAGE = "mmg/whoiswhodb/ent/v10";
//	private static final String PHP_FINDER_BASE_PACKAGE = "mmg/whoiswhodb/db/v10";
//	private static final String PHP_FINDER_SUFFIX = "Finder";
//
//	// TODO: for test only
//	private static final boolean TEST = true;
	
	private static final boolean OVERWRITE_EXISTING = true;
	
	public static void generateSQL( OMSMetaData metaData, String outputFile ) throws Exception
	{
		StringWriter sw = new StringWriter();
		VelocityContext ctx = new VelocityContext();
		ctx.put( "userId", USER_ID);
		ctx.put( "metaData", metaData );
		getEngine().mergeTemplate( SQL_MAIN_TEMPLATE, ctx, sw );
		System.out.println( sw );
		PrintWriter pw = new PrintWriter( new FileOutputStream( outputFile ) );
		pw.println( sw );
		pw.close();
	}
	
	public static void generateJava( OMSMetaData metaData, String baseDir, String basePackage ) throws Exception
	{
		VelocityEngine ve = getEngine();
		TemplateUtil util = new TemplateUtil();
		
		Namespace[] nsp = metaData.getAllNamespaces();
		for( int i = 0; i < nsp.length; i++ )
		{
			System.out.println( "**** NAMESPACE: " + nsp[ i ].getPath() );
			File entityDir = createJavaPackage( nsp[ i ], baseDir, basePackage, true );
			File finderDir = createJavaPackage( nsp[ i ], baseDir, basePackage, false );
			Scheme[] schemes = nsp[ i ].getSchemes();
			for( int j = 0; j < schemes.length; j++ )
			{
				Scheme[] children = getChildren( schemes[ j ], metaData );
				System.out.println( "CLASS: " + util.getEntityTypeName( basePackage + ".entity", schemes[ j ] ) );
				
				VelocityContext ctx = new VelocityContext();
				ctx.put( "text", new Text() );
				ctx.put( "util", util );
				ctx.put( "baseEntityPackage", basePackage + ".entity" );
				ctx.put( "baseFinderPackage", basePackage + ".finder" );
				ctx.put( "date", new Date() );
				ctx.put( "children", children );
				ctx.put( "scheme", schemes[ j ] );
				// base entity
				StringWriter sw = new StringWriter();
				ve.mergeTemplate( JAVA_BASE_ENTITY_TEMPLATE, ctx, sw );
				writeJavaFile( entityDir, "Base" + util.upCase( schemes[ j ].getName() ), sw, true );
				// entity
				sw = new StringWriter();
				ve.mergeTemplate( JAVA_ENTITY_TEMPLATE, ctx, sw );
				writeJavaFile( entityDir, util.upCase( schemes[ j ].getName() ), sw, false );
				System.out.println( "CLASS: " + util.getFinderTypeName( basePackage + ".finder", schemes[ j ] ) );
				// base finder
				sw = new StringWriter();
				ve.mergeTemplate( JAVA_BASE_FINDER_TEMPLATE, ctx, sw );
				writeJavaFile( finderDir, "Base" + util.upCase( schemes[ j ].getName() ) + "Finder", sw, true );
				// finder
				sw = new StringWriter();
				ve.mergeTemplate( JAVA_FINDER_TEMPLATE, ctx, sw );
				writeJavaFile( finderDir, util.upCase( schemes[ j ].getName() ) + "Finder", sw, false );
			}
		}
		System.out.println( "--- FINISHED ---");
	}
	
//	public void generatePHPClasses( OMSMetaData metaData ) throws Exception
//	{
//		VelocityEngine ve = getEngine();
//		PHPTemplateUtil util = new PHPTemplateUtil();
//		
//		Namespace[] nsp = metaData.getAllNamespaces();
//		for( int i = 0; i < nsp.length; i++ )
//		{
//			File baseEntityDir = createPHPPackage( nsp[ i ], PHP_BASE_ENTITY_BASE_PACKAGE );
//			File baseFinderDir = createPHPPackage( nsp[ i ], PHP_BASE_FINDER_BASE_PACKAGE );
//			File entityDir = createPHPPackage( nsp[ i ], PHP_ENTITY_BASE_PACKAGE );
//			File finderDir = createPHPPackage( nsp[ i ], PHP_FINDER_BASE_PACKAGE );
//			Scheme[] schemes = nsp[ i ].getSchemes();
//			for( int j = 0; j < schemes.length; j++ )
//			{
//				Scheme[] children = getChildren( schemes[ j ], metaData );
//				System.out.println( "CLASS: " + util.getEntityTypeNameBase( schemes[ j ] ) );
//				
//				VelocityContext ctx = new VelocityContext();
//				ctx.put( "text", new Text() );
//				ctx.put( "util", util );
//				ctx.put( "date", new Date() );
//				ctx.put( "children", children );
//				ctx.put( "scheme", schemes[ j ] );
//				ctx.put( "entityPackage", PHP_ENTITY_BASE_PACKAGE );
//				ctx.put( "finderPackage", PHP_FINDER_BASE_PACKAGE );
//				ctx.put( "baseEntityPackage", PHP_BASE_ENTITY_BASE_PACKAGE );
//				ctx.put( "baseFinderPackage", PHP_BASE_FINDER_BASE_PACKAGE );
//				// base entity
//				StringWriter sw = new StringWriter();
//				Velocity.mergeTemplate( PHP_BASE_ENTITY_TEMPLATE, ctx, sw );
//				writePHPFile( baseEntityDir, "Base" + util.upCase( schemes[ j ].getName() ), sw, true );
//				// entity
//				sw = new StringWriter();
//				Velocity.mergeTemplate( PHP_ENTITY_TEMPLATE, ctx, sw );
//				writePHPFile( entityDir, util.upCase( schemes[ j ].getName() ), sw, false );
//				// base finder
//				sw = new StringWriter();
//				Velocity.mergeTemplate( PHP_BASE_FINDER_TEMPLATE, ctx, sw );
//				writePHPFile( baseFinderDir, "Base" + util.upCase( schemes[ j ].getName() ) + PHP_FINDER_SUFFIX, sw, true );
//				// finder
//				sw = new StringWriter();
//				Velocity.mergeTemplate( PHP_FINDER_TEMPLATE, ctx, sw );
//				writePHPFile( finderDir, util.upCase( schemes[ j ].getName() ) + PHP_FINDER_SUFFIX, sw, false );
//			}
//		}
//		System.out.println( "--- FINISHED ---");
//	}

	private static Scheme[] getChildren( Scheme s, OMSMetaData metaData )
	{
		ArrayList result = new ArrayList();
		Namespace[] nsp = metaData.getAllNamespaces();
		for (int i = 0; i < nsp.length; i++)
		{
			Scheme[] schemes = nsp[ i ].getSchemes();
			for (int j = 0; j < schemes.length; j++)
			{
				if( schemes[ j ].isSubclassOf( s ) && !schemes[ j ].equals( s ) && !schemes[ j ].isAbstract() )
				{
					result.add( schemes[ j ] );
				}
			}
		}
		
		return (Scheme[]) result.toArray( new Scheme[ result.size() ] );
	}
	
	private static File createJavaPackage( Namespace nsp, String baseDir, String basePackage, boolean entity )
	{
		File base = new File( baseDir, Text.substitute( basePackage + ( entity ? ".entity" : ".finder" ), ".", "/" ) );
		File dir = new File( base, Text.substitute( Text.substitute( nsp.getPath(), "-", "_" ), ".", "_" ) );
		dir.mkdirs();
		return dir;
	}
	
//	private static File createPHPPackage( Namespace nsp, String base )
//	{
//		File basePath = new File( PHP_SOURCE_BASE_DIR, Text.substitute( base, ".", "/" ) );
//		File dir = new File( basePath, nsp.getPath() );
//		dir.mkdirs();
//		return dir;
//	}

	private static void writeJavaFile( File dir, String baseName, StringWriter sw, boolean overwriteAlways )
	throws FileNotFoundException
	{
		File f = new File( dir, baseName + ".java" );
		boolean overwrite = overwriteAlways || !f.exists();
		System.out.println( "FILE: " + f.getAbsolutePath() + ": " + ( overwrite ? "overwrite" : "save existing" ) );
		if( overwrite )
		{
			try
			{
				PrintWriter pw = new PrintWriter( new FileOutputStream( f.getAbsolutePath() ) );
				pw.println( sw );
				pw.close();
			}
			catch( Exception e )
			{
				e.printStackTrace();
			}
		}
	} 

	private static void writePHPFile( File dir, String baseName, StringWriter sw, boolean overwriteAlways )
	throws FileNotFoundException
	{
		File f = new File( dir, baseName + ".php" );
		boolean overwrite = overwriteAlways || !f.exists();
		System.out.println( "FILE: " + f.getAbsolutePath() + ": " + ( overwrite ? "overwrite" : "save existing" ) );
		if( overwrite )
		{
			PrintWriter pw = new PrintWriter( new FileOutputStream( f.getAbsolutePath() ) );
			pw.println( sw );
			pw.close();
		}
	} 

	
	private static void testArgs( String[] args )
	{
		if( args.length != 2 )
		{
			System.out.println( "Usage:" );
			System.out.println( "\tjava org.opencube.oms.tools.MetaDataGenerator <source_xml_file> <target_sql_file>" );
			System.exit( 1 );
		}
	}

	public static void main( String[] args )
	{
		try
		{
			execute( args );
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}
	}
	
	private static void execute( String[] args )
	throws Exception
	{
		HashMap params = null;
		try
		{
			params = readParams( args );
		}
		catch( Exception e )
		{
			System.out.println( e.getMessage() );
			System.out.println( getUsageString() );
			return;
		}
			
		OMSMetaData meta;
		if( params.get( "xml" ) != null )
		{
			meta = new XMIMetaData( (String) params.get( "xml" ), USER_ID );
		}
		else
		{
			meta = new OMSData( (String) params.get( "res_path" ), (String) params.get( "connection" ), Integer.parseInt( USER_ID ) ).getMetaData();
		}
		if( ( (String) params.get( "cmd" ) ).equalsIgnoreCase( "sql" ) )
		{
			
			generateSQL( meta, (String) params.get( "out" ) );
		}
		else
		{
			generateJava( meta, (String) params.get( "out_dir" ), (String) params.get( "package" ) );
		}
	}
	
	private static String getUsageString()
	{
		return "usage:\n" +
				"\n" +
				"sql [xml=<metadata xml path> | res_path=<resource path> connection=<connection_alias>] out=<sql output file>\n" +
				"or\n" +
				"java [xml=<metadata xml path> | res_path=<resource path> connection=<connection_alias>] out_dir=<directory for base package> package=<base package>\n";
	}
	
	private static HashMap readParams( String[] args ) throws Exception
	{
		HashMap map = new HashMap();
		if( args.length == 0 )
		{
			throw new Exception( "Command not defined" );
		}
		String command = args[ 0 ];
		map.put( "cmd", command );
		for (int i = 1; i < args.length; i++)
		{
			addParam( map, args[ i ] );
		}
		if( command.equalsIgnoreCase( "sql" ) )
		{
			if( map.get( "xml" ) == null && ( map.get( "res_path" ) == null || map.get( "connection" ) == null) )
			{
				throw new Exception( "Either the xml metadata file or resource path with connection alias must be defined" );
			}
			if( map.get( "out" ) == null )
			{
				throw new Exception( "The output sql file must be defined" );
			}
		}
		else if( command.equalsIgnoreCase( "java" ) )
		{
			if( map.get( "xml" ) == null && ( map.get( "res_path" ) == null || map.get( "connection" ) == null) )
			{
				throw new Exception( "Either the xml metadata file or resource path with connection alias must be defined" );
			}
			if( map.get( "out_dir" ) == null )
			{
				throw new Exception( "The output directory must be defined" );
			}
			if( map.get( "package" ) == null )
			{
				throw new Exception( "The base package must be defined" );
			}
		}
		else
		{
			throw new Exception( "Unknown command " + command );
		}
		return map;
	}
	
	private static void addParam( HashMap map, String string )
	throws Exception
	{
		StringTokenizer st = new StringTokenizer( string, "=" );
		String name = st.nextToken();
		if( st.hasMoreTokens() )
		{
			if( map.containsKey( name ) )
			{
				throw new Exception( "Duplicated parameter " + name );
			}
			map.put( name, st.nextToken() );
		}
	}
}
